/*
此C语言客户端来自一位华师学长，可是我连他的名字都不知道，只能在这里表示感谢了！
编译：gcc ccnu.c -o ccnu
运行：sudo ccnu 用户名 密码 网卡
*/
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h> 
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>   
#include <sys/stat.h>
#include <linux/if_arp.h>
#include <signal.h>

#define EAPOL_EAP    0
#define EAPOL_START  1    /*表明要求联网*/
#define EAPOL_LOGOFF 2    /*通知服务器下线*/

#define EAP_REQUEST  1    /*服务器要求客户端认证*/
#define EAP_RESPONSE 2    /*客户端确认服务器端的请求*/
#define EAP_SUCCESS  3    /*认证通过*/
#define EAP_FAILURE  4    /*认证失败*/
#define EAP_UNKNOWN 10

#define EAP_TYPE_ID  1    /*服务器端要求客户端发送用户名*/
#define EAP_TYPE_MD5 4
#define EAP_TYPE_H3C 7    /*服务器端要求客户端发送密码*/


const char addchar[] = "bjQ7SE8BZ3MqHhs3clMregcDY3Y=\x20\x20";
int count = 1;

/* the struct of packet data */
struct Packet
{
	unsigned char dst_mac[ETH_ALEN];/* dst mac address */
	unsigned char src_mac[ETH_ALEN];/* src mac address */
	unsigned char hdtype[2];        /* 0x088e instand of packet type */
	unsigned char version;          /* instand of ethernet network */
	unsigned char action_type;      /* 0:regular; 1:start; 2:logoff */
	unsigned char data_len1[2];     /* data length */
	unsigned char code;             /* server's response to client, 1, 2, 3, 4, 10 */
	unsigned char id;               /* seque number */
	unsigned char data_len2[2];     /* data length, include code, id, indentity_type, data and itself, equal to data_len */
	unsigned char identity_type;    /* 1:upload username; 7:upload password */
	unsigned char data[100];        /* data zone */
};

/* the struct of account information */
struct INFO
{
	char username[20];              /* username e.g: 2009210000 */
	char password[20];              /* password e.g: 123456*/
	char ifname[20];                /* interface name: e.g: eth0 */
};

/* print the interfaces' name, e.g:eth0, eth1... */
void
print_ifname()
{
	int i;
	int sockfd;
	struct ifconf ifc;
	struct ifreq *ifr;
	unsigned char buf[1024];
	
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		printf ("[ccnu] Error: (get_ifname): create socket failed!\n");
		exit(-1);
	}
	
	bzero (&ifc, sizeof(ifc));
	ifc.ifc_len = 1024;
	ifc.ifc_buf = (char *)&buf;
	if(ioctl(sockfd, SIOCGIFCONF, &ifc) < 0)
	{
		printf ("[ccnu] Error: (get_ifname): ioctl failed!\n");
		close (sockfd);
		exit (-1);
	}
	
	ifr = (struct ifreq *)buf;
	count = 0;
	for(i = (ifc.ifc_len/sizeof(struct ifreq)); i>0; i--)
	{
		if(strcmp(ifr->ifr_name, "lo"))
			printf ("%s ",ifr->ifr_name);
		ifr++;
	}	
	close (sockfd);
}

/* get mac address by interface name */
void
get_mac_addr_by_ifname (struct INFO *info, char *my_mac)
{
	int sockfd;
	struct ifreq ifr;
	if( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 )
	{
		printf ("[ccnu] Error: (get_mac_addr):create socket error!\n");
		exit(-1);
	}
	
	bzero (&ifr, sizeof (ifr));
	strcpy(ifr.ifr_name, info->ifname);
	if(ioctl(sockfd, SIOCGIFHWADDR, &ifr) < 0) {
		printf ("[ccnu] Error: (get_mac_addr):get hardware address failed!\n");
		close (sockfd);
		exit(-1);
	}
	memcpy (my_mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
	close (sockfd);
	/*
	printf ("[ccnu] address: %02x:%02x:%02x:%02x:%02x:%02x\n", ifr.ifr_addr.sa_data[0],ifr.ifr_addr.sa_data[1],\
		ifr.ifr_addr.sa_data[2], ifr.ifr_addr.sa_data[3], ifr.ifr_addr.sa_data[4], ifr.ifr_addr.sa_data[5]);
	*/
}

int
sock_init(struct INFO *info, struct sockaddr_ll *sll)
{
	int sockfd;
	struct ifreq ifr;

	if( (sockfd = socket(PF_PACKET, SOCK_RAW, ETH_P_PAE)) < 0)
	{
		printf ("[ccnu] Error: socket init failed!\n");
		exit (-1);
	}
	
	bzero (&ifr, sizeof(struct ifreq));
	bzero (sll, sizeof(struct sockaddr_ll));
	strcpy (ifr.ifr_name, info->ifname);
	if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) 
	{
		printf ("[ccnu] Error: (get_mac_addr):get interface index failed!\n");
		close(sockfd);
		return -1;
	}
	
	sll->sll_family = AF_PACKET;
	sll->sll_protocol = htons(ETH_P_PAE);
	sll->sll_ifindex = ifr.ifr_ifindex;
	
	if (bind(sockfd, (struct sockaddr *)sll, sizeof(struct sockaddr_ll)) < 0) 
	{
		printf ("[ccnu] Error: (get_mac_addr): bind socket failed!\n");
		close(sockfd);
		return -1;
	}
	
	return sockfd;
}


/* exit to program */
void 
exit_program()
{
	printf ("[ccnu] 发送联网请求超时!\n");
	printf ("[ccnu] 请确认:\n");
	printf ("[ccnu] 1)网线是否插好\n");
	printf ("[ccnu] 2)网卡名是否正确\n");
	printf ("[ccnu] 3)服务器是否正常工作\n");
	exit (0);	
}

int eapol_auth (struct Packet *packet, char *buf, struct INFO *info, char *sev_mac, char *my_mac)
{
	short int action;
	short int length;
	short int code;
	short int identity_type;
	packet = (struct Packet *)buf;
	
	memcpy (packet->dst_mac, sev_mac, ETH_ALEN);
	memcpy (packet->src_mac, my_mac, ETH_ALEN);
	
	/*
	length = ntohs (packet->data_len);
	printf ("[ccnu] %d\n", length);
	*/
	
	action = packet->action_type;
	length = packet->data_len1[1];
	
	if(action == EAPOL_EAP)
	{
		code = packet->code;
		if(code == EAP_REQUEST)
		{
			packet->code = EAP_RESPONSE;
			identity_type = packet->identity_type;
			if(identity_type == EAP_TYPE_ID)
			{
				/* upload username */
				buf[23] = 0x06;
				buf[24] = 0x07;
				strncpy (&buf[25], addchar, strlen(addchar));
				length = strlen(addchar) + 2;
				strncpy (&buf[23+length], info->username, strlen(info->username));
				length = length + strlen(info->username) + 5;
				if(count)
				{
					printf ("[ccnu]  ---> 上传用户名(%s)\n",info->username);				
				}
			}
			else if(identity_type == EAP_TYPE_H3C)
			{
				/* upload password + username */
				buf[23] = strlen(info->password);
	  			strncpy (&buf[24], info->password, strlen(info->password));
	  			length = strlen(info->password) + 1;
	  			strncpy (&buf[23+length], info->username, strlen(info->username));
	  			length = length + strlen(info->username) + 5;
	  			if(count)
	  				printf ("[ccnu]  ---> 上传密码...\n");	
			}
			packet->data_len1[0] = length >> 8;
			packet->data_len1[1] = length;
			packet->data_len2[0] = packet->data_len1[0];
	  		packet->data_len2[1] = length;
		}
		else if(code == EAP_SUCCESS)
		{
			printf ("[ccnu]  ---> 认证成功!\n");
		}
		else if(code == EAP_FAILURE)
		{
			if(count)
			{
				int i, j;
				char info[200];
				printf ("[ccnu]  ---> 认证失败!\n");
			
				for(i = 0, j = 0; i < length; i++, j++)
				{	
					info[i] = buf[32+j];
					if(info[i] == '.' || info[i] == ',')
					{
						info[++i] = '\n';
						info[++i] = ' ';
					}
				}
				info[i++] = '\n';
				info[i] = '\0';
				if(length >= 14)
					printf ("[ccnu] Error: %s", info);
			}
			exit(-1);
		}
		else if(code == EAP_UNKNOWN)
		{
			length = 0;
		}
		else {
			printf ("[ccnu]  ---> Unknown EAP Code!\n");
			exit (-1);
		}
	}
	else
	{
		printf ("[ccnu]  ---> Unknown EAPoL Code!\n");
		return -1;
	}
	
	return length;
}


void
packet_init(char *buf, char *dst_mac, char *src_mac, short int type)
{
	struct Packet *packet;
	bzero(buf, sizeof(buf));
	packet = (struct Packet *)buf;
	memcpy (packet->dst_mac, dst_mac, ETH_ALEN);
	memcpy (packet->src_mac, src_mac, ETH_ALEN);
	packet->hdtype[0] = 0x88;
	packet->hdtype[1] = 0x8e;
	packet->version = 0x01;
	packet->action_type = type;
	packet->data_len1[0] = 0;
	packet->data_len1[1] = 0;
}

int
main(int argc, char **argv)
{
	struct Packet *packet;
	struct INFO *info;
	struct sockaddr_ll *sll;
	
	char buf[200];
	char my_mac[ETH_ALEN+2];
	char sev_mac[ETH_ALEN+2] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x03};
	
	int sockfd, sll_len, data_len;
	int action;
	uid_t uid;
	
	/* set uid */
	uid = getuid();
	if(uid)
	{
		printf ("[ccnu] 请以root权限运行!\n");
		exit(0);
	} 
	setuid(uid);
	
	if(argc != 4 ){
		printf ("[ccnu] 用法: ccnu <username> <password> <interface>\n");
		exit (0);	
	}
	else
		action = EAPOL_START;
	
	info = (struct INFO *)malloc (sizeof (struct INFO));
	if(info == NULL)
	{
		printf ("[ccnu] Error: malloc info failed!\n");
		exit (-1);
	}
	
	sll = (struct sockaddr_ll *)malloc (sizeof (struct sockaddr_ll));
	if(sll == NULL)
	{
		printf ("[ccnu] Error: malloc sll failed!\n");
		exit (-1);
	}

	strcpy(info->username, argv[1]);
	strcpy(info->password, argv[2]);
	strcpy(info->ifname, argv[3]);

	get_mac_addr_by_ifname(info, my_mac);
	sockfd = sock_init (info, sll);
	if(sockfd < 0)
	{
		printf ("[ccnu] Error: socket init failed!\n");
		exit (-1);
	}
	
	packet_init(buf, sev_mac, my_mac, EAPOL_START);
	if(action == EAPOL_START)
		printf ("[ccnu]  ---> 请求连接...\n");
	
	signal (SIGALRM, exit_program);
	alarm(10);
	
	sll_len = sizeof (struct sockaddr_ll);		
	packet = (struct Packet *)buf;
	
	/* get server's mac address */
	sendto (sockfd, &buf, 18, 0, (struct sockaddr *)sll, sizeof(struct sockaddr_ll));
	bzero (buf, sizeof (buf));
	recvfrom (sockfd, &buf, sizeof(buf), 0, (struct sockaddr *)sll, (socklen_t *)&sll_len);
	memcpy (sev_mac, packet->src_mac, ETH_ALEN);
	
	packet_init(buf, sev_mac, my_mac, action);
	if(action == EAPOL_START)
		printf ("[ccnu]  ---> 开始认证...\n");
	sendto (sockfd, &buf, 18, 0, (struct sockaddr *)sll, sizeof(struct sockaddr_ll));
	
	while (1)
	{
		bzero (buf, sizeof(buf));
		recvfrom (sockfd, &buf, sizeof(buf), 0, (struct sockaddr *)sll, (socklen_t *)&sll_len);

		if(!memcmp(my_mac, packet->dst_mac, ETH_ALEN))
		{
			data_len = eapol_auth (packet, buf, info, sev_mac, my_mac) + 18;
			if(data_len > 18)
			{
				if(data_len < 60)
					data_len = 60;
				sendto (sockfd, &buf, data_len, 0, (struct sockaddr *)sll, sizeof(struct sockaddr_ll));	
			}
		}
	}
	
	free (info);
	free (sll);
	close (sockfd);
	return 0;
}